<?php
//Класс обеспечивающий работу с множествами объектов
class Set {
    //Информация о классе объектов
    protected $class_name;
    protected $dbf;
    protected $table;
    
    //Запрос, полученные по нему объекты и их id
    protected $query;
    protected $objects = array();
    protected $ids = '0';
    
    protected $is_empty = TRUE;
    protected $is_constructed = FALSE;
    
    protected $total_num = 0;
    
    //Передаём запрос, который должен вернуть интересующее нас множество записей
    //Или их id
    function __construct($query = FALSE, $is_query_for_ids = FALSE) {
        extract(Object::getDBInfo($this->class_name));
        
        $this->dbf = $field_prefix;
        $this->table = $table_name;
        
		//По умолчанию получаем все элементы
		if ($query === FALSE)
		    $query = q::obj($this->class_name)->select()->end;
		
        //В случае необходимости преобразуем запрос так, чтобы он возвращал множество записей  
        if (!$is_query_for_ids) 
            $this->query = $query;
        else
            $this->query = $this->createFullQuery($query);
            
        $this->query = $this->createCountQuery($this->query);
    }
    
    //Изменяем запрос так, чтобы он возвращал общее количество записей
    //Это нужно для постраничной работы со списками объектов
    protected function createCountQuery($query) {
        if (preg_match('/^(\s* SELECT)(.*)/six', $query, $m))
             $query = $m[1] . ' SQL_CALC_FOUND_ROWS' . $m[2];
        return $query;
    }
    
    protected function construct() {
        if (!$this->is_constructed) {
            $rows = DB::select($this->query);

            if (!$rows) return FALSE;
            
            //Получаем общее количество записей
            $num = DB::selectOne(q::count());
            $this->total_num = $num['num'];
            $this->is_empty = FALSE;
            
            //Создаём массив объектов и заполняем их данными из БД
            foreach ($rows as $row) {
                $object = new $this->class_name();
                $object->forcedSetFields($row);
                $id = $object->getId();
                $objects[$id] = $object;
                $this->ids .= ','.$id;
            }
                    
            $this->objects = $objects;
            $this->is_constructed = TRUE;
        }
    }
    
    //Модифицируем запрос если исходный возвращает только id
    protected function createFullQuery($init_query) {
        $query = 'SELECT * FROM `'.$this->table.'`';
        $query .= 'WHERE `'.$this->dbf.'id` IN (';
        $query .= $init_query.')';
        return $query;
    }
    
    //Работа с постраничным выводом
    public function setPage($page_num, $page_size) {
        $this->query .= ' LIMIT '.(($page_num-1)*$page_size).', '.$page_size;
        return $this;
    }
    
    public function getOne() {
       $this->construct();
       if ($this->is_empty)
           return FALSE;
       else
           foreach ($this->objects as $object)
              return $object;
    }
    
    public function get() {
        $this->construct();
        return ($this->is_empty)? FALSE: $this->objects;
    }
    
    //По древовидной структуре объектов создаёт древовидную структуру массива
    //При необходимости значения ковентируются в защищённый для вывода в html формат
    public function getArray($field_data, $is_html = FALSE) {
    	if ($is_html)
		  $get_method = 'getHTMLFields';
		else
		  $get_method = 'getFields';
		
        $this->construct();
        
        $result = array();
        if ($this->is_empty) return $result;
        
        foreach ($this->objects as $obj) {
            $row = array();
            foreach ($field_data as $entity=>$fields) {
                if ($entity == $this->class_name) {
                    $row = $row + $obj->$get_method($fields);
				}
                else {
                    //Вычисляем псевдоним, если он есть
                    $pos = strpos($entity, ' as ');
                    if ($pos !== false) {
                        $parts = explode(' as ', $entity);
                        $entity = $parts[0];
                        $pseudonym = $parts[1];
                    }
                    else
                       $pseudonym = $entity;
                    
                    $joined_entities = $obj->getJoined($entity);
                    if ($joined_entities) {
                        if (!is_array($joined_entities))                       
                           $row[$pseudonym] = $joined_entities->$get_method($fields);
                        else {
                            $row[$pseudonym] = array();
                            foreach ($joined_entities as $joined_entity)
                                $row[$pseudonym][] = $joined_entity->$get_method($fields);
                        }
                    }
                    else
                       $row[$pseudonym] = NULL;
                }
            }
            $result[] = $row;           
        }
        return $result;
    }
    
    //Присоединение других сущностей к исходным,
    //$className - класс присоединяемой сущности
    //$join_query - запрос к таблице присоединяемых сущностей
    //который также должен возвращать поле parent_id, хранящее id исходных сущностей
    //$multiply - присоединяется ли к каждой исходной сущности несколько других (в противном случае - одна)
    protected function _join($className, $join_query, $multiply = TRUE) {
        $rows = DB::select($join_query); 

        foreach ($rows as $row) {
            $id = $row['parent_id'];
            unset($row['parent_id']);
            $obj = new $className();
            $obj->forcedSetFields($row);
            $this->objects[$id]->join($className, $obj, $multiply);
        }
                
        return $this;
    }
    
    
    /*public function joinChilds($tie) {
        $this->construct();
        
        $sel = '*, `'.$tie->getDBInfo('owner_field').'` AS `parent_id`';
        $join_query = q::tie(new ProblemHasTags)->select($sel, FALSE)->joinChilds()->whereOwners($this->ids)->end;
       
        return $this->_join($tie->child(), $join_query);
    } */   
    
    //Пересортировка результатов выборки
    public function resort($ids) {
        $new_obj = array();
        $ids = explode(',', $ids);
        foreach ($ids as $id) {
            $new_obj[] = $this->objects[$id];
        }
        $this->objects = $new_obj;
    }
    
    //$entity - класс сущности, которая должна быть приджойнена
    //$join_condition - условие по которому производится join
    //$multiply - присоединяется ли к каждой исходной сущности несколько других (в противном случае - одна)
    /*public function join($entity, $join_condition, $multiple = TRUE) {
        $this->construct();
        
        extract(Object::getDBInfo($entity));
        $rows = DB::select(Query::joinQuery($this->table, $this->dbf,$this->ids, $table_name, $field_prefix, 'owner')); 

        foreach ($rows as $row) {
            $id = $row['parent_id'];
            unset($row['parent_id']);
            $user = new $entity();
            $user->forcedSetFields($row);
            $this->objects[$id]->join($entity, $user, $multiply);
        }
                
        return $this;
    }*/
	
	public function joinByField($field, $class_name) {
        $this->construct();
        extract(Object::getDBInfo($class_name));        
        $sel = '*, `'.$this->dbf.'id` AS `parent_id`';
        $join_query = q::obj($this->class_name)->select($sel, FALSE)->innerJoin($table_name, $this->dbf.$field, $field_prefix.'id')->where_in('id', $this->ids, FALSE)->end;
        //$join_query = 'SELECT *, `prb_id` AS `parent_id` FROM `problems` INNER JOIN `users` ON `prb_user_id` = `usr_id` WHERE `prb_id` IN ('.$this->ids.') ';
        return $this->_join($class_name, $join_query, FALSE);
    }
	
	
    
    public function isEmpty()
    {
        $this->construct();
        return $this->is_empty; 
    }
    
    public function getTotal() {
        return $this->total_num;
    }
}
?>